<!DOCTYPE html>
<html lang="zh-cn">
<head>
  <meta charset="UTF-8">
  <title>monster</title>
  <script src="./pixi_5.3.7.min.js"></script>
  <script src="./stats.min.js"></script>
  <script src="./pixi-spine.js"></script>
  <style>
    * {
      padding: 0;
      margin: 0;
    }
    html, body, #page-spine {
      width: 100%;
      height: 100%;
    }
    #canvas {
      display: block;
    }
    #play-btn {
      position: absolute;
      bottom: 20px;
      left: 50%;
      transform: translate(-50%, 0);
      color: darkcyan;
      background-color: lightcyan;
      width: 150px;
      height: 45px;
      cursor: pointer;
      border-radius: 4px;
      z-index: 1;
      text-align: center;
      border: 1px solid darkcyan;
      outline: none;
    }
    #play-btn:hover {
      background-color: #ccf9f9;
    }
    #play-btn:active {
      background-color: #c0eeee;
    }
    #file {
      position: absolute;
      z-index: 0;
      visibility: hidden;
      left: 0;
      top: 0;
    }
  </style>
</head>
<body>
<div id="page-spine">
  <canvas id="canvas"></canvas>
  <input id="file" type="file" accept="audio/*" @change="onFileChange($event)">
  <button id="play-btn">选择音乐</button>
</div>
<script>

  (function () {
    window.PIXI = PIXI

    let pageWidth = 0
    let pageHeight = 0
    let pageX = 0
    let pageY = 0
    let gameRunning = false
    let dancing = false

    let basePos = { x: 0, y: 0 }

    const pixiSpine = PIXI.spine
    const coreSpine = PIXI.spine.core

    const stats = new Stats()
    document.body.appendChild(stats.dom)

    const $canvas = document.querySelector('#canvas')
    onPageResize()

    const renderer = new PIXI.Renderer({
      view: $canvas,
      width: pageWidth,
      height: pageHeight,
      resolution: window.devicePixelRatio,
      transparent: true,
      autoDensity: true,
      antialias: false,
    })

    const loader = new PIXI.Loader()

    const ticker = new PIXI.Ticker()

    const stage = new PIXI.Container()
    stage.name = 'stage'

    let spineMonster
    let state
    let skeleton
    let controlEye
    let controlHead
    let controlBody

    let controlEyeX = 0
    let controlEyeY = 0

    let controlEyeXTarget = 0
    let controlEyeYTarget = 0

    let controlBodyX = 0
    let controlBodyY = 0

    let controlBodyXTarget = 0
    let controlBodyYTarget = 0

    const eyeBasePos = { x: 0, y: 0 }
    const bodyBasePos = { x: 0, y: 0 }

    function onPageResize (e) {
      pageWidth = window.innerWidth
      pageHeight = window.innerHeight
      basePos.x = pageWidth * 0.5
      basePos.y = pageHeight * 0.7
      if (gameRunning) {
        renderer.resize(pageWidth, pageHeight)
        spineMonster.position.set(basePos.x, basePos.y)
      }
    }

    let analyser
    let bufferLength = 0
    let dataArray = []

    let now = performance.now()
    let last = now
    const loop = function (delta) {
      stats.begin()

      if (gameRunning) {
        now = performance.now()
        if (now - last > 100) {
          last = now
        }

        if (dancing) {
          analyser.getByteFrequencyData(dataArray)
          let i
          let audioVal = 0
          for (i = 0; i < bufferLength; i++) {
            audioVal += dataArray[i]
          }
          audioVal /= bufferLength // 获取均值
          controlBodyYTarget = -bodyBasePos.y + audioVal
          controlBodyYTarget = controlBodyYTarget * 0.3
          controlBodyYTarget = Math.max(30, Math.min(240, controlBodyYTarget))
        }

        controlEyeX += (controlEyeXTarget - controlEyeX) * 0.1
        controlEyeY += (controlEyeYTarget - controlEyeY) * 0.1
        controlBodyX += (controlBodyXTarget - controlBodyX) * 0.1
        controlBodyY += (controlBodyYTarget - controlBodyY) * 0.5

        controlEye.x = controlEyeX
        controlEye.y = controlEyeY
        controlBody.x = controlBodyX
        controlBody.y = controlBodyY
      }
      renderer.render(stage)

      stats.end()
    }
    ticker.add(loop)

    loader.add('monster', './output/monster.json')
      .load(function (loader, resources) {
        spineMonster = new pixiSpine.Spine(resources.monster.spineData)
        spineMonster.name = 'monster'
        spineMonster.position.set(basePos.x, basePos.y)
        stage.addChild(spineMonster)
        state = spineMonster.state
        skeleton = spineMonster.skeleton

        controlEye = skeleton.findBone('eye') // 获取眼球控制的骨骼
        controlHead = skeleton.findBone('head') // head是eye的父级，eye在head的本地坐标系内移动
        controlBody = skeleton.findBone('body')

        // 记录眼球相对位置
        controlEyeX = controlEyeXTarget = controlEye.x
        controlEyeY = controlEyeYTarget = controlEye.y
        eyeBasePos.x = controlHead.worldX
        eyeBasePos.y = controlHead.worldY

        bodyBasePos.x = controlBody.worldX
        bodyBasePos.y = controlBody.worldY
        controlBodyX = controlBodyXTarget = controlBody.x
        controlBodyY = controlBodyYTarget = controlBody.y

        stage.interactive = true
        stage.on('pointertap', () => {
          const entry = state.setAnimation(0, 'laugh', false)
        })
        stage.on('pointermove', (e) => {
          pageX = e.data.global.x
          pageY = e.data.global.y
          // 修改眼球在本地坐标系中的位置
          controlEyeXTarget = pageX - (basePos.x + eyeBasePos.x)
          controlEyeYTarget = -(pageY - (basePos.y + eyeBasePos.y)) // y轴翻转
        })

        state.timeScale = 1
        ticker.start()
        gameRunning = true
      })

    window.onresize = onPageResize

    const $btn = document.getElementById('play-btn')
    const $file = document.getElementById('file')
    let audio = null
    let timer = -1
    $file.onchange = function () {
      const file = $file.files[0]
      if (!file) return
      if (audio) audio.pause()
      audio = new Audio()
      audio.loop = true
      audio.addEventListener('canplay', event => {
        audio.play()
        let flag = false
        clearInterval(timer)
        timer = setInterval(function () {
          flag = !flag
          controlBodyXTarget = flag ? 50 : -50
        }, 1000)
        dancing = true
      })
      audio.src = URL.createObjectURL(file)

      // 创建分析器节点
      const audioCtx = new (window.AudioContext || window.webkitAudioContext)()
      analyser = audioCtx.createAnalyser()
      analyser.fftSize = 32 // 采样
      // 把分析器节点连接到声源
      const source = audioCtx.createMediaElementSource(audio)
      source.connect(analyser)
      // 分析器节点输出到另一个节点（不输出也可以正常使用。但前提是它必须与一个声源相连，直接或者通过其他节点间接相连都可以）
      analyser.connect(audioCtx.destination)
      bufferLength = analyser.frequencyBinCount // 值是FFT的一半
      dataArray = new Uint8Array(bufferLength)
    }
    $btn.onclick = function () {
      $file.click()
    }

  })()

</script>
</body>
</html>